﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TransportAbstractionLibrary.Exceptions;
using System.IO.Compression;
using System.IO;

namespace TransportAbstractionLibrary.Layer
{
    public sealed class CompressionLayer : IProtocolLowerLayer, IProtocolUpperLayer
    {
        bool initialized;
        Guid id = new Guid("D0437744-C4D5-4a51-8EAA-4073A15D5F86");
        IProtocolUpperLayer upperLayer;
        IProtocolLowerLayer lowerLayer;

        public CompressionLayer()
        {

        }

        #region IProtocolLowerLayer Members

        public void Bind(IProtocolUpperLayer upperLayer)
        {
            if (this.upperLayer != null)
            {
                OnException(new AlreadyBoundException(this));
                return;
            }
            this.upperLayer = upperLayer;
            upperLayer.Bind(this);
        }

        public void Send(byte[] data)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
                {
                    byte[] lengthBytes = BitConverter.GetBytes(data.Length);
                    deflateStream.Write(lengthBytes, 0, 4);
                    deflateStream.Flush();
                    deflateStream.Write(data, 0, data.Length);
                    deflateStream.Flush();
                }
                lowerLayer.Send(memoryStream.ToArray());
            }
        }

        public IProtocolUpperLayer UpperLayer
        {
            get { return upperLayer; }
        }

        #endregion

        #region IProtocolUpperLayer Members

        void IProtocolUpperLayer.Bind(IProtocolLowerLayer lowerLayer)
        {
            if ((lowerLayer.LayerType & (LayerType.Package | LayerType.Order | LayerType.Integrity)) != (LayerType.Package | LayerType.Order | LayerType.Integrity))
            {
                throw new InvalidLayerOrderException(this, "The CompressionLayer requiers a package, order and Integrity layer to be between it and the Transport layer");
            }
            this.lowerLayer = lowerLayer;
        }

        public void OnException(TALException talException)
        {
            upperLayer.OnException(talException);
        }

        void IProtocolUpperLayer.Receive(byte[] data)
        {
            if (!initialized)
            {
                if (BinaryHelper.CompareByte(id.ToByteArray(), data))
                {
                    initialized = true;
                    upperLayer.Initialize();
                    return;
                }
                else
                {
                    OnException(new InvalidLayerOrderException(this));
                    return;
                }
            }
            using (MemoryStream memoryStream = new MemoryStream(data))
            {
                using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
                {
                    byte[] lengthBytes = new byte[4];
                    deflateStream.Flush();
                    deflateStream.Read(lengthBytes, 0, 4);
                    int length = BitConverter.ToInt32(lengthBytes, 0);
                    byte[] buffer = new byte[length];
                    deflateStream.Flush();
                    deflateStream.Read(buffer, 0, length);

                    upperLayer.Receive(buffer);
                }
            }
        }

        public IProtocolLowerLayer LowerLayer
        {
            get { return lowerLayer; }
        }

        void IProtocolUpperLayer.ConnectionClosed()
        {
            upperLayer.ConnectionClosed();
        }

        #endregion

        #region IProtocolBase Members

        public Guid Id
        {
            get
            {
                return id;
            }
        }

        public LayerType LayerType
        {
            get 
            {
                if (lowerLayer != null)
                {
                    return lowerLayer.LayerType | LayerType.Compression;
                }
                else
                {
                    return LayerType.Compression;
                }
            }
        }

        void IProtocolBase.Initialize()
        {
            //We should send some data and validate that its the same layer at this level!

            //upperLayer.Initialize();
            if (lowerLayer.SuppressInitialization)
            {
                initialized = true;
                upperLayer.Initialize();
            }
            else
            {
                lowerLayer.Send(id.ToByteArray());
            }
        }

        bool IProtocolBase.SuppressInitialization
        {
            get { return lowerLayer.SuppressInitialization; }
        }

       

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            lowerLayer.Dispose();
        }

        #endregion

    }
}
